package org.nnsoft.guice.rocoto.variables;

/*
 *    Copyright 2009-2012 The 99 Software Foundation
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

import java.util.ArrayList;
import java.util.List;

/**
 * Basic implementation of a tree.
 *
 * @param <T>
 */
final class Tree<T>
{

    /** Current tree node data */
    private T data;

    /** Parent node */
    private Tree<T> parent = null;

    /** Children */
    private List<Tree<T>> children = new ArrayList<Tree<T>>();

    /**
     * Default constructor
     *
     * @param data
     */
    public Tree( T data )
    {
        this.data = data;
    }

    /**
     * @return True if parent is not null
     */
    public boolean isRoot()
    {
        return parent == null;
    }

    /**
     * @return True if no children
     */
    public boolean isLeaf()
    {
        return children.isEmpty();
    }

    /**
     * Add a new leaf to this tree
     *
     * @param child
     * @return Tree node of the newly added leaf
     */
    public Tree<T> addLeaf( T child )
    {
        Tree<T> leaf = new Tree<T>( child );
        leaf.parent = this;
        children.add( leaf );
        return leaf;
    }

    /**
     * @return Parent node, or null if this node is root
     */
    public Tree<T> getParent()
    {
        return parent;
    }

    /**
     * Remove this tree from its parent, if any.
     */
    public void removeFromParent()
    {
        if ( !isRoot() )
        {
            getParent().removeSubtree( this );
        }
    }

    /**
     * Remove given subtree
     *
     * @param subtree
     */
    public void removeSubtree( Tree<T> subtree )
    {
        if ( children.remove( subtree ) )
        {
            subtree.parent = null;
        }
    }

    /**
     * @return Node data
     */
    public T getData()
    {
        return data;
    }

    /**
     *
     * @return Node depth
     */
    public int getDepth()
    {
        int depth = 0;
        Tree<T> curr = parent;
        while ( curr != null )
        {
            curr = curr.parent;
            depth++;
        }
        return depth;
    }

    /**
     * @return Subtrees
     */
    public List<Tree<T>> getChildren()
    {
        return children;
    }

    /**
     * Add a subtree, provided tree is not modified.
     *
     * @param subtree
     * @return subtree copy added
     */
    public Tree<T> addSubtree( Tree<T> subtree )
    {
        Tree<T> copy = addLeaf( subtree.data );
        copy.children = new ArrayList<Tree<T>>( subtree.children );
        return copy;
    }

    /**
     * @param element
     * @return true if element is contained in this node subtrees.
     */
    public boolean inSubtrees( T element )
    {
        for ( Tree<T> child : getChildren() )
        {
            if ( child.isElement( element ) || child.inSubtrees( element ) )
            {
                return true;
            }
        }
        return false;
    }

    /**
     * @param element
     * @return True if element is equal to this node data.
     */
    public boolean isElement( T element )
    {
        return ( data.equals( element ) );
    }

    /**
     *
     * @param element
     * @return true if element is is in the ancestors of this node
     */
    public boolean inAncestors( T element )
    {
        if ( !isRoot() )
        {
            return getParent().isElement( element ) || getParent().inAncestors( element );
        }
        return false;
    }

    /**
     * @return Root node, this if current node is root
     */
    public Tree<T> getRoot()
    {
        if ( isRoot() )
        {
            return this;
        }
        return getParent().getRoot();
    }

    @Override
    public String toString()
    {
        StringBuilder buffer = new StringBuilder();
        toString( buffer, 0 );
        return buffer.toString();
    }

    private void toString( StringBuilder buffer, int level )
    {
        // Create proper indent
        // Search for next cousins in each level
        StringBuilder indent = new StringBuilder();
        Tree<T> prev;
        Tree<T> curr = this;
        for ( int i = level - 1; i >= 0; i-- )
        {
            prev = curr;
            curr = prev.parent;
            if ( i == level - 1 )
            {
                indent.append( " _|" );
            }
            else
            {
                indent.append( "  " );

                if ( i < level && curr.children.indexOf( prev ) < curr.children.size() - 1 )
                {
                    indent.append( "|" );
                }
                else
                {
                    indent.append( " " );
                }
            }
        }
        buffer.append( indent.reverse() );

        // Print data
        buffer.append( getData() ).append( "\n" );

        // Print subtrees
        for ( Tree<T> child : getChildren() )
        {
            child.toString( buffer, level + 1 );
        }
    }

}
